package org.xtext.runtime.term;

import java.util.Map;

import org.xtext.runtime.type.IType;
import org.xtext.runtime.type.Type;
import org.xtext.runtime.type.TypeException;
import org.xtext.runtime.util.Bindings;
import org.xtext.runtime.util.ILogicVisitor;


public class Modulo implements ITerm {
	private IType type;
	private ITerm left, right;
	
	public Modulo(ITerm left, ITerm right) throws TypeException {
		this.left = left;
		this.right = right;
		
		// infer type & check
		type = Type.selectIntegerType(left.type(), right.type());
		if (type == null) {
			throw new TypeException("Modulo '%' only allows integer or long values");
		}
	}
	
	@Override
	public IType type() {
		return type;
	}

	@Override
	public ITerm copy() {
		try {
			return new Modulo(left.copy(), right.copy());
		} catch (TypeException e) {
			return null;
		}
	}

	@Override
	public Object value() {
		throw new UnsupportedOperationException();
	}

	public String toString() {
		return left + " % " + right;
	}

	@Override
	public Modulo copy(Map<String, Variable> vars) {
		ITerm l = left;
		ITerm r = right;
		if (Variable.class.isInstance(l)) {
			Variable variable = vars.get(((Variable) l).identifier());
			if (variable == null) {
				variable = (Variable) l.copy();
				vars.put(variable.identifier(), variable);
			}
			l = variable;
		} else {
			l = l.copy(vars);
		}
		if (Variable.class.isInstance(r)) {
			Variable variable = vars.get(((Variable) r).identifier());
			if (variable == null) {
				variable = (Variable) r.copy();
				vars.put(variable.identifier(), variable);
			}
			r = variable;
		} else {
			r = r.copy(vars);
		}
		try {
			return new Modulo(l, r);
		} catch (TypeException e) {
			return null;
		}
	}

	@Override
	public Modulo apply(Bindings bindings) {
		if (Variable.class.isInstance(left)) {
			Variable variable = (Variable) left;
			if (variable.isFree()) {
				ITerm value = bindings.getStoredValue(variable);
				if (value != null) {
					try {
						variable.assign(value);
					} catch (TypeException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				}
			}
		} else {
			left.apply(bindings);
		}
		if (Variable.class.isInstance(right)) {
			Variable variable = (Variable) right;
			if (variable.isFree()) {
				ITerm value = bindings.getStoredValue(variable);
				if (value != null) {
					try {
						variable.assign(value);
					} catch (TypeException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				}
			}
		} else {
			right.apply(bindings);
		}
		return this;
	}

	@Override
	public boolean hasFreeVariables() {
		return left.hasFreeVariables() || right.hasFreeVariables();
	}

	@Override
	public Object accept(ILogicVisitor visitor) {
		return visitor.visit(this);
	}

	public ITerm left() {
		return left;
	}

	public ITerm right() {
		return right;
	}
}
